﻿using System;
using System.ComponentModel;
using System.Globalization;
using System.Text;
using System.Threading;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace ClickLand.Web.UI.WebControls
{
	/// <summary>Control personalizado de Calendario.</summary>
	[DefaultProperty("Value"),
    ToolboxData("<{0}:ClickLandCalendar runat='server'/>"),
    Designer("ClickLand.Web.UI.WebControls.ClickLandCalendar")]
    public class ClickLandCalendar: TextBox, IPostBackDataHandler
    {
		#region Variables privadas

		private Uri _UrlScripts;
		private Uri _UrlImagen;

		private string _Lenguaje;
		private Estilo _Tema;
		private DateTime _DeshabilitarFechasAnteriores;
		private DateTime _DeshabilitarFechasPosteriores;
		private AlineacionVertical _AlineacionVertical;
		private AlineacionHorizontal _AlineacionHorizontal;
		private string _FormatoFecha;
		//Se utiliza un ImageButton y no una Imagen o no funciona bien en Firefox
		//y similares (No soporta Disabled = true de imagenes vía script)
		//Y es necesario que no requiera postback
		private ImageButton _btnCalendar = new ImageButton();
		#endregion

		#region Constructores
		/// <summary>Constructor por defecto.</summary>
        public ClickLandCalendar()
		{
			_Lenguaje = @"es";
			_Tema = Estilo.Blue;
			_DeshabilitarFechasPosteriores = DateTime.MaxValue;
			_AlineacionVertical = AlineacionVertical.CompletamenteAbajo;
			_AlineacionHorizontal = AlineacionHorizontal.Izquierda;
			Thread.CurrentThread.CurrentCulture = new CultureInfo("es-VE");
			_FormatoFecha = Thread.CurrentThread.CurrentCulture.DateTimeFormat.ShortDatePattern;
		}
		#endregion

		#region Eventos
		/// <summary>Variable para manejar el evento OnChange.</summary>
		public event EventHandler Change;

		/// <summary>Evento para el post data.</summary>
		public new void RaisePostDataChangedEvent()
		{
			OnChange(EventArgs.Empty);
		}

		/// <summary>Ocurre al cambiar datos entre un post back y el siguiente.</summary>
		/// <param name="e">Argumentos.</param>
		public virtual void OnChange(EventArgs e)
		{
			if(Change != null)
				Change(this, e);
		}

		#endregion

		#region Enums

		/// <summary>Lista las posibles posiciones verticales con respecto al control base.</summary>
		public enum AlineacionVertical
		{
			/// <summary>Encima del control.</summary>
			CompletamenteArriba,
			/// <summary>Arriba del control.</summary>
			Arriba,
			/// <summary>Centrado con respecto al control.</summary>
			Centro,
			/// <summary>Abajo con respecto al control.</summary>
			Abajo,
			/// <summary>Debajo del control.</summary>
			CompletamenteAbajo
		}

		/// <summary>Lista las posibles posiciones horizontales con respecto al control base.</summary>
		public enum AlineacionHorizontal
		{
			/// <summary>A la izquierda del control.</summary>
			CompletamenteIzquierda,
			/// <summary>Del lado izquierdo del control.</summary>
			Izquierda,
			/// <summary>Al centro del control.</summary>
			Centro,
			/// <summary>Del lado derecho del control.</summary>
			Derecha,
			/// <summary>A la derecha del control.</summary>
			CompletamenteDerecha
		}

		/// <summary>Lista los posibles valores de stilos preconstruidos.</summary>
		public enum Estilo
		{
			/// <summary>Esquema Azul.</summary>
			Blue,
			/// <summary>Esquema Verde.</summary>
			green,
		}

		#endregion

		#region Propiedades
		/// <summary>Permite obtener y asignar el Culture del control.</summary>
		[Bindable(true),
		Category("Apariencia"),
		Description("Cultura del lenguaje"),
		DefaultValue("es")]
		public string Lenguaje
		{
			get { return (_Lenguaje == null) ? "es" : _Lenguaje; }
			set
			{
				if(value != null)
					_Lenguaje = value.ToLower(CultureInfo.InvariantCulture);
			}
		}

		/// <summary>Permite asignar un tema.</summary>
		[Bindable(true),
		Category("Apariencia"),
		Description("Estilo del calendario"),
		DefaultValue("Estilo.Blue")]
		public Estilo Tema
		{
			get { return _Tema; }
			set { _Tema = value; }
		}

		/// <summary>Permite desabilitar fechas anteriores a la fecha suministrada.</summary>
		[Bindable(true),
		Category("Apariencia"),
		Description("Deshabilitar en el calendario fechas anteriores a la indicada. Si se indica como atributo del control en el aspx, el formato utilizado es el de invariant culture (MM/dd/yyyy)."),
		DefaultValue("DateTime.MinValue")]
		public DateTime DeshabilitarFechasAnteriores
		{
			get { return _DeshabilitarFechasAnteriores; }
			set { _DeshabilitarFechasAnteriores = value; }
		}

		/// <summary>Permite desabilitar fechas posteriores a la fecha suministrada.</summary>
		[Bindable(true), Category("Apariencia"),
		Description("Deshabilitar en el calendario fechas posteriores a la indicada. Si se indica como atributo del control en el aspx, el formato utilizado es el de invariant culture (MM/dd/yyyy)."),
		DefaultValue("DateTime.MinValue")]
		public DateTime DeshabilitarFechasPosteriores
		{
			get { return _DeshabilitarFechasPosteriores; }
			set { _DeshabilitarFechasPosteriores = value; }
		}

		/// <summary>Posición vertical del calendario.</summary>
		[Bindable(true),
		Category("Apariencia"),
		Description("Posición vertical del calendario"),
		DefaultValue("AlineacionVertical.CompletamenteAbajo")]
		public AlineacionVertical PosicionVertical
		{
			get { return _AlineacionVertical; }
			set { _AlineacionVertical = value; }
		}

		/// <summary>Posición horizontal del calendario.</summary>
		[Bindable(true),
		Category("Apariencia"),
		Description("Posición horizontal del calendario"),
		DefaultValue("AlineacionHorizontal.Izquierda")]
		public AlineacionHorizontal PosicionHorizontal
		{
			get { return _AlineacionHorizontal; }
			set { _AlineacionHorizontal = value; }
		}

		/// <summary>Formato de fecha.</summary>
		[Bindable(true),
		Category("Apariencia"),
		Description("Formato de fecha")]
		public string FormatoFecha
		{
			get { return (_FormatoFecha == null) ? @"dd/MM/yyyy" : _FormatoFecha; }
			set { _FormatoFecha = value; }
		}

		/// <summary>Url del archivo .js externo.</summary>
		[Bindable(true),
		Category("Apariencia"),
		Description("Url del archivo .js externo")]
		public Uri UrlScripts
		{
			get { return _UrlScripts; }
			set { _UrlScripts = value; }
		}

		/// <summary>Url de la imagen.</summary>
		[Bindable(true),
		Category("Apariencia"),
		Description("Url de la imagen")]
		public Uri UrlImagen
		{
			get { return _UrlImagen; }
			set { _UrlImagen = value; }
		}
		#endregion

		#region Funciones Sobreescritas
		/// <summary>Obtiene o asigna la clase CSS asociada al control.</summary>
		/// <remarks>Si no es asignada, el control utiliza la clase CSS por defecto definida en ClickLandWebControlLibrary.Constantes.</remarks>
		public override string CssClass
		{
			get
			{
				if(string.IsNullOrEmpty(base.CssClass))
					return "DefCalendar";
				else
					return base.CssClass;
			}
			set
			{
				base.CssClass = value;
			}
		}

		/// <summary>Obtiene o asigna el skin a aplicar al control.</summary>
		/// <remarks>Si no es asignada, el control utiliza el skin por defecto definido en ClickLandWebControlLibrary.Constantes.</remarks>
		public override string SkinID
		{
			get
			{
				if(string.IsNullOrEmpty(base.SkinID))
                    return "DefCalendar";
				else
					return base.SkinID;
			}
			set
			{
				base.SkinID = value;
			}
		}

		/// <summary>Permite habilitar o desabilitar el control.</summary>
		public override bool Enabled
		{
			get
			{
				return base.Enabled;
			}
			set
			{
				_btnCalendar.Enabled = value;
				base.Enabled = value;
			}
		}

		/// <summary>Render this control to the output parameter specified.</summary>
		/// <param name="writer">The HTML writer to write out to.</param>
		protected override void Render(HtmlTextWriter writer)
		{
			MaxLength = 10;

			base.Render(writer);
			_btnCalendar.RenderControl(writer);
			using(Page _page = (Page)System.Web.HttpContext.Current.Handler)
			{
				ClientScriptManager _scriptManager = _page.ClientScript;
				Type rsType = GetType();

				if(!_scriptManager.IsClientScriptBlockRegistered(ClientID + @"ClickLandCalendar.Setup"))
				{
					IFormatProvider provider = CultureInfo.InvariantCulture;
					StringBuilder str = new StringBuilder();
					str.Append("\n<script type=\"text/javascript\"> \n");
					if(DeshabilitarFechas())
					{
						// Los meses en JavaScript van del 0 al 11.
						str.AppendFormat(provider, "var minDate = new Date({0}, {1}, {2});\n", _DeshabilitarFechasAnteriores.Year, _DeshabilitarFechasAnteriores.Month - 1, _DeshabilitarFechasAnteriores.Day);
						str.AppendFormat(provider, "var maxDate = new Date({0}, {1}, {2});\n", _DeshabilitarFechasPosteriores.Year, _DeshabilitarFechasPosteriores.Month - 1, _DeshabilitarFechasPosteriores.Day + 1);
					}
					str.Append("if(Calendar)\n");
					str.Append("Calendar.setup({\n");
					str.AppendFormat(provider, "inputField:\"{0}\"\n", ClientID);
					str.AppendFormat(provider, ",ifFormat:\"{0}\"\n", ConvertToJSDateFormat(FormatoFecha));
					str.AppendFormat(provider, ",button:\"{0}btn\"\n", ClientID);
					str.AppendFormat(provider, ",align:\"{0}\"\n", RecuperarAlineacion());
					str.Append(",electric:\"false\"\n");
					if(DeshabilitarFechas())
					{
						str.Append(",dateStatusFunc:dateStatus\n");
						str.AppendFormat(provider, ",range:[{0}, {1}]\n", _DeshabilitarFechasAnteriores.Year, _DeshabilitarFechasPosteriores.Year);
					}
					str.Append("});\n");
					str.Append("</script>\n");
                    _scriptManager.RegisterClientScriptBlock(rsType, ClientID + @"ClickLandCalendar.Setup", string.Empty);
					writer.Write(str);
				}
			}
		}

		/// <summary>Crea los objetos internos del control.</summary>
		protected override void CreateChildControls()
		{
			IFormatProvider provider = CultureInfo.InvariantCulture;
			ClientScriptManager _scriptManager = Page.ClientScript;
			Type rsType = GetType();

			if(_UrlImagen != null && _UrlImagen.ToString().Length > 0)
			{
				_btnCalendar.ImageUrl = _UrlImagen.ToString();
			}
			else
			{
                _btnCalendar.ImageUrl = _scriptManager.GetWebResourceUrl(rsType, "ClickLand.Web.UI.WebControls.Recursos.img.gif");
				_btnCalendar.CssClass = @"calImg";
			}
			_btnCalendar.ID = ID + @"btn";
			Controls.Add(_btnCalendar);

			if(!_scriptManager.IsClientScriptBlockRegistered(@"ClickLandCalendar.calendar"))
				_scriptManager.RegisterClientScriptInclude(@"ClickLandCalendar.calendar", _scriptManager.GetWebResourceUrl(rsType, @"ClickLand.Web.UI.WebControls.Recursos.calendar_stripped.js"));
			if(!_scriptManager.IsClientScriptBlockRegistered(@"ClickLandWebControlLibrary.calendar_lang"))
				_scriptManager.RegisterClientScriptInclude(@"ClickLandCalendar.calendar_lang", _scriptManager.GetWebResourceUrl(rsType, string.Format(provider, @"ClickLand.Web.UI.WebControls.Recursos.calendar-{0}.js", Lenguaje)));
			if(!_scriptManager.IsClientScriptBlockRegistered(@"ClickLandCalendar.calendar_setup"))
				_scriptManager.RegisterClientScriptInclude(@"ClickLandCalendar.calendar_setup", _scriptManager.GetWebResourceUrl(rsType, @"ClickLand.Web.UI.WebControls.Recursos.calendar-setup_stripped.js"));
			if(!_scriptManager.IsClientScriptBlockRegistered(@"ClickLandCalendar.calendar_helper"))
				_scriptManager.RegisterClientScriptInclude(@"ClickLandCalendar.calendar_helper", _scriptManager.GetWebResourceUrl(rsType, @"ClickLand.Web.UI.WebControls.Recursos.calendar-helper.js"));
			// Registro el thema CSS en la pagina
			if(!_scriptManager.IsClientScriptBlockRegistered(@"ClickLandCalendar.calendar_theme"))
			{
				string resourceName = string.Format(provider, "ClickLand.Web.UI.WebControls.Recursos.{0}.css", RecuperarTema());
				string includeLocation = _scriptManager.GetWebResourceUrl(rsType, resourceName);
				string includeTemplate = "<link rel=\"stylesheet\" text=\"text/css\" href=\"" + includeLocation + "\"/>";
				LiteralControl include = new LiteralControl(includeTemplate);
				Page.Header.Controls.Add(include);
				_scriptManager.RegisterClientScriptBlock(rsType, @"ClickLandCalendar.calendar_theme", string.Empty);
			}
			base.CreateChildControls();
		}
		#endregion

		#region Funciones auxiliares

		static string ConvertToJSDateFormat(string f)
		{
			f = f.Replace(@"dd", @"%d");
			f = f.Replace(@"MM", @"%m");
			f = f.Replace(@"yyyy", @"%Y");
			return f;
		}

		private string RecuperarTema()
		{
			string tema;
			switch(Tema)
			{
				case Estilo.green:
					tema = @"calendar-greem";
					break;
				default:
					tema = @"calendar-blue";
					break;
			}
            return @"calendar-blue";
		}

		private string RecuperarAlineacion()
		{
			return RecuperarAlineacionVertical() + RecuperarAlineacionHorizontal();
		}

		private string RecuperarAlineacionVertical()
		{
			string alineacion;
			switch(PosicionVertical)
			{
				case AlineacionVertical.CompletamenteArriba:
					// Completely above the reference element (bottom margin of the calendar
					// aligned to the top margin of the element).
					alineacion = @"T";
					break;
				case AlineacionVertical.Arriba:
					// Above the element but may overlap it (bottom margin of the calendar
					// aligned to the bottom margin of the element).
					alineacion = @"t";
					break;
				case AlineacionVertical.Centro:
					// The calendar displays vertically centered to the reference element.
					// It might overlap it (that depends on the horizontal alignment).
					alineacion = @"c";
					break;
				case AlineacionVertical.Abajo:
					// Below the element but may overlap it (top margin of the calendar
					// aligned to the top margin of the element).
					alineacion = @"b";
					break;
				case AlineacionVertical.CompletamenteAbajo:
					// Completely below the element (top margin of the calendar
					// aligned to the bottom margin of the element).
					alineacion = @"B";
					break;
				default:
					alineacion = @"B";
					break;
			}
			return alineacion;
		}

		private string RecuperarAlineacionHorizontal()
		{
			string alineacion;
			switch(PosicionHorizontal)
			{
				case AlineacionHorizontal.CompletamenteIzquierda:
					// Completely to the left of the reference element (right margin of the calendar
					// aligned to the left margin of the element).
					alineacion = @"L";
					break;
				case AlineacionHorizontal.Izquierda:
					// To the left of the element but may overlap it (left margin of the calendar
					// aligned to the left margin of the element).
					alineacion = @"l";
					break;
				case AlineacionHorizontal.Centro:
					// Horizontally centered to the element. Might overlap it,
					// depending on the vertical alignment.
					alineacion = @"c";
					break;
				case AlineacionHorizontal.Derecha:
					// To the right of the element but may overlap it (right margin of the calendar
					// aligned to the right margin of the element).
					alineacion = @"r";
					break;
				case AlineacionHorizontal.CompletamenteDerecha:
					// Completely to the right of the element (left margin of the calendar
					// aligned to the right margin of the element).
					alineacion = @"R";
					break;
				default:
					alineacion = @"l";
					break;
			}
			return alineacion;
		}

		private bool DeshabilitarFechas()
		{
			return (_DeshabilitarFechasAnteriores != DateTime.MinValue || _DeshabilitarFechasPosteriores != DateTime.MaxValue);
		}
		#endregion
	}
}